package main.java.com.yn.xgame;

import com.google.protobuf.GeneratedMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.yn.xgame.NettyProtobufHandler;
import com.yn.xgame.pb.ClientAccessProto;
import com.yn.xgame.pb.ClientGateProto;
import com.yn.xgame.pb.GateClientProto;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * NettyClient
 * Created by wangboo on 2017/7/12.
 */
public class NettyClient implements NettyProtobufHandler.Callback {

    private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);

    private static final int maxFrameLength = 0xffff;
    private static final int lengthFieldOffset = 0;
    private static final int lengthFieldLength = 2;
    private static final int lengthAdjustment = 1;
    private static final int initialBytesToStrip = 2;

    private String ip;
    private int port;
    private boolean isGate;
    private Bootstrap client;
    EventLoopGroup group = new NioEventLoopGroup();
    ChannelHandlerContext ctx;
    private Lock lock = new ReentrantLock();
    private Condition conCond = lock.newCondition();

    public NettyClient(String ip, int port, boolean isGate) {
        this.ip = ip;
        this.port = port;
        this.isGate = isGate;
    }

    public void connect() throws InterruptedException {
        client = new Bootstrap();
        client
                .group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, lengthFieldOffset,
                                lengthFieldLength, lengthAdjustment, initialBytesToStrip));
                        ch.pipeline().addLast(new MessageToByteEncoder<GeneratedMessage>() {
                            @Override
                            protected void encode(ChannelHandlerContext ctx, GeneratedMessage msg, ByteBuf out) throws Exception {
                                logger.debug("MessageToByteEncoder write: {}", msg);
                                byte[] bin = msg.toByteArray();
                                byte lenHigh = (byte) ((bin.length >> 8) & 0xff);
                                byte lenLow = (byte) (bin.length & 0xff);
                                out.writeByte(lenHigh);
                                out.writeByte(lenLow);
                                out.writeByte(0);
                                out.writeBytes(bin);
                            }
                        });
                        ch.pipeline().addLast(new NettyProtobufHandler(NettyClient.this) {
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                System.out.println("channelActive");
                                NettyClient.this.ctx = ctx;
                                NettyClient.this.lock.lock();
                                NettyClient.this.conCond.signal();
                                NettyClient.this.lock.unlock();
                            }

                            @Override
                            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                System.out.println("channelInactive");
                                NettyClient.this.ctx = null;
                            }
                        });
                    }
                });
        client.connect(ip, port);
        lock.lock();
        try {
            conCond.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void send(ClientAccessProto.C2AMessage req) {
        if (ctx == null) throw new RuntimeException("ctx is not connected");
        ctx.writeAndFlush(req);
    }

    public void send(ClientGateProto.C2GMessage req) {
        if (ctx == null) throw new RuntimeException("ctx is not connected");
        ctx.writeAndFlush(req);
    }

    @Override
    public void handle(ChannelHandlerContext ctx, byte[] bin) {
        try {
            if (isGate) {
                GateClientProto.G2CMessage resp = GateClientProto.G2CMessage.parseFrom(bin);
                logger.debug("recv from gate: {}", resp);
            } else {
                ClientAccessProto.A2CMessage resp = ClientAccessProto.A2CMessage.parseFrom(bin);
                logger.debug("recv from access: {}", resp);
            }


        } catch (InvalidProtocolBufferException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) {
    }
}
